package it.tukano.swingdeco.input;

import it.tukano.collections.LinkedListExt;
import it.tukano.collections.ListExt;
import it.tukano.fun.Function0;
import it.tukano.fun.Function1;
import it.tukano.fun.Function2;
import it.tukano.swingdeco.containers.JPanelExt;
import it.tukano.swingdeco.controls.JButtonExt;
import it.tukano.swingdeco.controls.JSpinnerExt;
import it.tukano.swingdeco.list.JListExt;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.border.EtchedBorder;

public class PopupColorChooser {

    private Color currentColor = Color.BLACK;

    private ColorBox BACKGROUND_GRID = new ColorBox(100,100);
    private final Runnable SPINNER_LISTENER = new Runnable() {

        @Override
        public void run() {
            currentColor = new Color(RSPINNER.getFloatValue(), GSPINNER.getFloatValue(), BSPINNER.getFloatValue(), ASPINNER.getFloatValue());
            BACKGROUND_GRID.setForeground(currentColor);
        }
    };
    private final JSpinnerExt RSPINNER = new JSpinnerExt().withNumericModel(0.0, 0.0, 1.0, 0.02);
    private final JSpinnerExt GSPINNER = new JSpinnerExt().withNumericModel(0.0, 0.0, 1.0, 0.02);
    private final JSpinnerExt BSPINNER = new JSpinnerExt().withNumericModel(0.0, 0.0, 1.0, 0.02);
    private final JSpinnerExt ASPINNER = new JSpinnerExt().withNumericModel(1.0, 0.0, 1.0, 0.02);
    
    private final JPanelExt RGB_PANEL = new JPanelExt(new BorderLayout(10, 10))
            .append(BACKGROUND_GRID, BorderLayout.CENTER)
            .append(new JPanelExt(new GridLayout(4, 2, 4, 4))
                .append(new JLabel("RED"), RSPINNER, new JLabel("GREEN"), GSPINNER, new JLabel("BLUE"), BSPINNER, new JLabel("ALPHA"), ASPINNER).withPreferredSize(150, 100),
                BorderLayout.EAST);

    private final JTabbedPane PANELS = new JTabbedPane();

    private final ListExt<JLabel> PRESET_BUTTONS = new LinkedListExt<JLabel>().addAll(new Function0<JLabel>() {
        MouseListener listener = new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if(SwingUtilities.isLeftMouseButton(e)) {
                    Color c = e.getComponent().getBackground();
                    float[] rgba = c.getComponents(null);
                    RSPINNER.setValue(rgba[0]);
                    GSPINNER.setValue(rgba[1]);
                    BSPINNER.setValue(rgba[2]);
                }
            }
        };
        int count = 0;

        @Override
        public JLabel apply() {
            if(count++ < 40) {
                JLabel button = new JLabel();
                button.setOpaque(true);
                button.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
                button.setPreferredSize(new Dimension(16, 16));
                button.addMouseListener(listener);
                return button;
            } else {
                return null;
            }
        }
    });

    private final ListExt<PaletteComponent> PALETTES = new LinkedListExt<PaletteComponent>()
            .append(new PaletteComponent(Color.BLACK, Color.WHITE))
            .append(new PaletteComponent(Color.RED, Color.YELLOW))
            .append(new PaletteComponent(Color.CYAN, Color.GREEN))
            .append(new PaletteComponent(Color.PINK, Color.BLUE))
            .append(new PaletteComponent(Color.ORANGE, Color.MAGENTA));

    private final JListExt PALETTE_LIST = new JListExt().withItems(PALETTES).withRenderer(new Function2<Object, Boolean, Component>() {

        @Override
        public Component apply(Object a, Boolean b) {
            return (Component) a;
        }
    }).onSelectionSet(new Runnable() {

        @Override
        public void run() {
            PaletteComponent selectedValue = (PaletteComponent) PALETTE_LIST.getSelectedValue();
            if(selectedValue != null) {
                selectedValue.applyBackgrounds(PRESET_BUTTONS);
            }
        }
    });
    private final JPanelExt PRESET_COLORS = new JPanelExt(new GridLayout(4, PRESET_BUTTONS.size() / 4, 2, 2))
            .appendAll(PRESET_BUTTONS);

    private final JPanelExt LEFT_AREA = new JPanelExt(new BorderLayout(8,8))
            .append(PANELS, BorderLayout.CENTER)
            .append(PRESET_COLORS, BorderLayout.SOUTH);

    private final JPanelExt PALETTE_PANEL = new JPanelExt(new BorderLayout(8,8))
            .appendWithTitles("Palettes", PALETTE_LIST.getScroller());

    private final JButtonExt ACCEPT = new JButtonExt("Ok");
    private final JButtonExt CANCEL = new JButtonExt("Cancel");

    private final JPanelExt BUTTONS_PANEL = new JPanelExt(new BorderLayout(8,8))
            .append(new JSeparator(JSeparator.HORIZONTAL), BorderLayout.NORTH)
            .append(new JPanelExt(new FlowLayout(FlowLayout.TRAILING)).append(ACCEPT, CANCEL), BorderLayout.CENTER);

    private JPanelExt DISPLAY = new JPanelExt(new BorderLayout(8,8))
            .append(LEFT_AREA, BorderLayout.CENTER)
            .append(PALETTE_PANEL, BorderLayout.EAST)
            .append(BUTTONS_PANEL, BorderLayout.SOUTH);

    /**
     * Instance initializer
     */
    public PopupColorChooser() {
        RSPINNER.addListener(SPINNER_LISTENER);
        GSPINNER.addListener(SPINNER_LISTENER);
        BSPINNER.addListener(SPINNER_LISTENER);
        ASPINNER.addListener(SPINNER_LISTENER);
        PANELS.addTab("RGBA", RGB_PANEL);
        PALETTES.get(0).applyBackgrounds(PRESET_BUTTONS);
    }

    public void popup(Component parent, Point location, Color color, final Function1<Color, ?> onAccept) {
        for(ActionListener listener : ACCEPT.getActionListeners()) {
            ACCEPT.removeActionListener(listener);
        }
        for (ActionListener actionListener : CANCEL.getActionListeners()) {
            CANCEL.removeActionListener(actionListener);
        }
        setCurrentColor(color);
        final JPopupMenu popup = new JPopupMenu();
        CANCEL.addActionListener(new ActionListener(){

            @Override
            public void actionPerformed(ActionEvent e) {
                popup.setVisible(false);
            }
        });
        ACCEPT.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                popup.setVisible(false);
                if(onAccept != null) onAccept.apply(currentColor);
            }
        });
        popup.add(DISPLAY);
        popup.show(parent, location.x, location.y);
    }

    private void setCurrentColor(Color color) {
        if(color != null) {
            currentColor = color;
            float[] components = currentColor.getComponents(null);
            RSPINNER.setValue(components[0]);
            GSPINNER.setValue(components[1]);
            BSPINNER.setValue(components[2]);
            ASPINNER.setValue(components[3]);
        }
    }

    class PaletteComponent extends JLabel {

        /* Serial version of this class */
        private static final long serialVersionUID = 0;

        final Color MIN, MAX;

        PaletteComponent(Color min, Color max) {
            setOpaque(true);
            setPreferredSize(new Dimension(100, 20));
            MIN = min;
            MAX = max;
            setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
        }

        @Override
        protected void paintComponent(Graphics graphics) {
            Insets insets = getInsets();
            super.paintComponent(graphics);
            Graphics2D g = (Graphics2D) graphics;
            g.setPaint(new GradientPaint(0, 0, MIN, getWidth() - 1, 0, MAX));
            g.fillRect(insets.left, insets.top, getWidth() - insets.left - insets.right, getHeight() - insets.top - insets.bottom);
        }

        void applyBackgrounds(List<? extends Component> components) {
            float c = Math.max(1, components.size());
            int dr = Math.round((MAX.getRed() - MIN.getRed()) / c);
            int dg = Math.round((MAX.getGreen() - MIN.getGreen()) / c);
            int db = Math.round((MAX.getBlue() - MIN.getBlue()) / c);
            int da = Math.round((MAX.getAlpha() - MIN.getAlpha()) / c);
            for (int i= 0; i < components.size(); i++) {
                int r = MIN.getRed() + i * dr;
                int g = MIN.getGreen() + i * dg;
                int b = MIN.getBlue() + i * db;
                int a = MIN.getAlpha() + i * da;
                Color color = new Color(clamp(r), clamp(g), clamp(b), clamp(a));
                components.get(i).setBackground(color);
            }
            components.get(0).setBackground(MIN);
            components.get(components.size() - 1).setBackground(MAX);
        }

        private int clamp(int r) {
            if(r < 0) return 0;
            if(r > 255) return 255;
            return r;
        }
    }
}
